﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Transactions;
using Ue.Extensions;

namespace Ue.Infrastructure.Data
{
    public class UnitOfWork : IUnitOfWork
    {
        private IList<IWork> works;

        public UnitOfWork()
        {
            this.works = new List<IWork>();
        }

        #region IUnitOfWork Members

        public void RegisterAdded<TEntity,TKey>(TEntity entity, IUnitOfWorkRepository<TEntity,TKey> repository)
            where TEntity : IEntity<TKey>
        {
            new WorkOfAdd<TEntity, TKey>(entity, repository)
                .AddOrUpdateWorkCaches(this.works);
        }

        public void RegisterModified<TEntity, TKey>(TEntity entity, IUnitOfWorkRepository<TEntity, TKey> repository)
            where TEntity : IEntity<TKey>
        {
            new WorkOfUpdate<TEntity, TKey>(entity, repository)
                .AddOrUpdateWorkCaches(this.works);
        }

        public void RegisterDeleted<TEntity, TKey>(TEntity entity, IUnitOfWorkRepository<TEntity, TKey> repository)
            where TEntity : IEntity<TKey>
        {
            new WorkOfDelete<TEntity, TKey>(entity, repository)
                .AddOrUpdateWorkCaches(this.works);
        }

        public void Commit()
        {
            using (TransactionScope scope = new TransactionScope())
            {
                foreach (var item in this.works.OrderBy(item=>item.Priority))
                {
                    item.Action();
                }

                scope.Complete();
            }

            this.works.Clear();
        }

        #endregion

        #region Work

        public interface IWork
        {
            Action Action { get; }

            int Priority { get; }
        }

        public abstract class WorkBase<TEntity,TKey> : IWork
            where TEntity : IEntity<TKey>
        {
            public WorkBase(TEntity entity, IUnitOfWorkRepository<TEntity, TKey> repository)
            {
                this.Repository = repository;
                this.Entity = entity;
            }

            public IUnitOfWorkRepository<TEntity, TKey> Repository { get; private set; }

            public TEntity Entity { get; private set; }

            public Action Action { get; protected set; }

            public abstract int Priority { get; }

            public void AddOrUpdateWorkCaches(IList<IWork> works)
            {
                if (!works.Contains(this))
                {
                    this.Action = this.BuildAction(works);

                    works.Add(this);
                }
            }

            protected abstract Action BuildAction(IList<IWork> works);

            #region Equals

            public override bool Equals(object obj)
            {
                var label = obj as WorkBase<TEntity, TKey>;
                if (label != null)
                {
                    return this.Entity.Equals(label.Entity) && this.Priority == label.Priority;
                }

                return false;
            }

            public override int GetHashCode()
            {
                return this.Entity.GetCombineHashCode(this.Priority);
            }

            #endregion
        }

        public class WorkOfDelete<TEntity, TKey> : WorkBase<TEntity,TKey>
            where TEntity : IEntity<TKey>
        {
            public WorkOfDelete(TEntity entity, IUnitOfWorkRepository<TEntity, TKey> repository) 
                : base(entity,repository) { }

            public override int Priority 
            {
                get { return 10; } 
            }

            protected override Action BuildAction(IList<IWork> works)
            {
                return () => this.Repository.PersistDeletedItem(this.Entity);
            }
        }

        public class WorkOfAdd<TEntity, TKey> : WorkBase<TEntity,TKey>
            where TEntity : IEntity<TKey>
        {
            public WorkOfAdd(TEntity entity, IUnitOfWorkRepository<TEntity, TKey> repository) 
                : base(entity,repository) { }

            public override int Priority
            {
                get { return 50; }
            }

            protected override Action BuildAction(IList<IWork> works)
            {
                return () => this.Repository.PersistNewItem(this.Entity);
            }
        }

        public class WorkOfUpdate<TEntity, TKey> : WorkBase<TEntity, TKey>
            where TEntity : IEntity<TKey>
        {
            public WorkOfUpdate(TEntity entity, IUnitOfWorkRepository<TEntity, TKey> repository) 
                : base(entity,repository) { }

            public override int Priority
            {
                get { return 100; }
            }

            protected override Action BuildAction(IList<IWork> works)
            {
                return () => this.Repository.PersistUpdatedItem(this.Entity);
            }
        }

        #endregion
    }
}
